Module Imports¶

In [1]:
import os
import pandas as pd

import numpy as np

import matplotlib.pyplot as plt
import seaborn as sns

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Rescaling, Conv2D, MaxPooling2D, Dense, Flatten, Dropout, BatchNormalization
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from tensorflow.keras.optimizers import Adam

from sklearn.metrics import confusion_matrix, precision_score, recall_score, accuracy_score, f1_score

Data import and formatting¶

In [2]:
# location path of the datasets
train_dir = "/Users/preslav/Downloads/cw_cop528/imageset/train"
test_dir = "/Users/preslav/Downloads/cw_cop528/imageset/val"
In [3]:
# setting a common standard for the pixel values, to fall in
# setting a validation and training split 
train_data = ImageDataGenerator(rescale=1./255,
                               validation_split=0.2)
val_data = ImageDataGenerator(rescale=1/255, 
                              validation_split=0.2)
test_data = ImageDataGenerator(rescale=1./255)
In [4]:
# importing the data batches and setting their properties 
train_batches = train_data.flow_from_directory(directory = train_dir, 
                                               target_size = (224, 224), 
                                               subset = "training",
                                               batch_size = 32, 
                                               seed = 2)
validation_batches = val_data.flow_from_directory(directory = train_dir, 
                                                  target_size = (224, 224), 
                                                  subset = "validation",
                                                  batch_size = 32, 
                                                  seed = 2)
test_batches = test_data.flow_from_directory(directory = test_dir, 
                                             target_size = (224, 224),
                                             batch_size = 32, 
                                             shuffle = False)
Found 7578 images belonging to 10 classes.
Found 1891 images belonging to 10 classes.
Found 3925 images belonging to 10 classes.
In [5]:
# import of the class labels names and their total number 
class_names = list(train_batches.class_indices.keys())
num_classes = len(class_names)
print(class_names)
print(num_classes)
['building', 'dog', 'fish', 'gas_station', 'golf', 'musician', 'parachute', 'radio', 'saw', 'vehicle']
10

Initial data (image) exploration¶

In [6]:
# import the test dataset, so that an unprocessed image can be taken from it for examination.
# further data is shuffled for the process taking place at the last section of this notebook
test_data_shuffled = tf.keras.utils.image_dataset_from_directory(test_dir, shuffle = True, seed = 247)
Found 3925 files belonging to 10 classes.
Metal device set to: Apple M2
2023-03-17 11:04:00.835374: I tensorflow/core/common_runtime/pluggable_device/pluggable_device_factory.cc:305] Could not identify NUMA node of platform GPU ID 0, defaulting to 0. Your kernel may not have been built with NUMA support.
2023-03-17 11:04:00.835524: I tensorflow/core/common_runtime/pluggable_device/pluggable_device_factory.cc:271] Created TensorFlow device (/job:localhost/replica:0/task:0/device:GPU:0 with 0 MB memory) -> physical PluggableDevice (device: 0, name: METAL, pci bus id: <undefined>)
In [7]:
def data_iterator(data):
    '''
    This function returns a batch,
    given its provided with a dataset,
    that has been devided into batches.
    '''
    iterator = data.as_numpy_iterator()
    batch = iterator.next()
    return batch
In [8]:
# get a batch from test_data_shuffled
example_batch = data_iterator(test_data_shuffled)

# get an image and its corresponding label from the batch
image, label = example_batch[0][18], example_batch[1][18]

# get the pixel value range
pixel_min = np.min(image)
pixel_max = np.max(image)
print("Pixel value range: [{}, {}]".format(pixel_min, pixel_max))

# plot the image and its respective actual label as title
plt.imshow(image.astype(np.uint8))
plt.title(class_names[label])
plt.colorbar()
plt.show()
2023-03-17 11:04:00.883582: W tensorflow/core/platform/profile_utils/cpu_utils.cc:128] Failed to get CPU frequency: 0 Hz
Pixel value range: [0.0, 255.0]

Visualizing few images¶

In [9]:
# importing a batch of images and labels
img, lbl = next(train_batches)
In [10]:
# plotting 9 images and their respective class labels
plt.figure(figsize = (12, 12))
for i in range(9):
    class_index = np.argmax(lbl[i])
    plt.subplot(3, 3, i + 1)
    plt.imshow(img[i])
    plt.title(class_names[class_index])
    plt.axis("off")
plt.tight_layout()
plt.show()

Building Model Architecture¶

In [11]:
# setting the model's architecture
model_base = Sequential([
    Conv2D(16, (3,3), 1, activation = "relu"),
    MaxPooling2D(),
    Conv2D(32, (3,3), 1, activation = "relu"),
    Conv2D(32, (3,3), 1, activation = "relu"),
    MaxPooling2D(),
    Conv2D(32, (3,3), 1, activation = "relu"),
    Conv2D(32, (3,3), 1, activation = "relu"),
    MaxPooling2D(),
    Flatten(),
    Dense(256, activation = "relu"),
    Dense(num_classes, activation = "softmax")
])
In [12]:
# setting the model's loss function, gradient descnet optimizer and evaluation metrics 
model_base.compile(optimizer = "adam", loss = "categorical_crossentropy", metrics = ["accuracy"])
In [13]:
# performing training of the model with the training batches and validation batches 
epochs = 20
history_base= model_base.fit(train_batches,
                      validation_data = validation_batches,
                      epochs = epochs)
Epoch 1/20
2023-03-17 11:04:02.565846: I tensorflow/core/grappler/optimizers/custom_graph_optimizer_registry.cc:113] Plugin optimizer for device_type GPU is enabled.
237/237 [==============================] - ETA: 0s - loss: 1.8461 - accuracy: 0.3515
2023-03-17 11:04:20.855685: I tensorflow/core/grappler/optimizers/custom_graph_optimizer_registry.cc:113] Plugin optimizer for device_type GPU is enabled.
237/237 [==============================] - 22s 90ms/step - loss: 1.8461 - accuracy: 0.3515 - val_loss: 1.5436 - val_accuracy: 0.4966
Epoch 2/20
237/237 [==============================] - 21s 89ms/step - loss: 1.3036 - accuracy: 0.5645 - val_loss: 1.2939 - val_accuracy: 0.5828
Epoch 3/20
237/237 [==============================] - 21s 88ms/step - loss: 0.9802 - accuracy: 0.6767 - val_loss: 1.2480 - val_accuracy: 0.5970
Epoch 4/20
237/237 [==============================] - 21s 89ms/step - loss: 0.6295 - accuracy: 0.7928 - val_loss: 1.3356 - val_accuracy: 0.5902
Epoch 5/20
237/237 [==============================] - 21s 90ms/step - loss: 0.3028 - accuracy: 0.9000 - val_loss: 1.9532 - val_accuracy: 0.5643
Epoch 6/20
237/237 [==============================] - 21s 90ms/step - loss: 0.1449 - accuracy: 0.9512 - val_loss: 2.3566 - val_accuracy: 0.5595
Epoch 7/20
237/237 [==============================] - 21s 89ms/step - loss: 0.0869 - accuracy: 0.9715 - val_loss: 2.4054 - val_accuracy: 0.5579
Epoch 8/20
237/237 [==============================] - 21s 90ms/step - loss: 0.0768 - accuracy: 0.9764 - val_loss: 2.6037 - val_accuracy: 0.5664
Epoch 9/20
237/237 [==============================] - 21s 89ms/step - loss: 0.0456 - accuracy: 0.9854 - val_loss: 3.0778 - val_accuracy: 0.5420
Epoch 10/20
237/237 [==============================] - 22s 92ms/step - loss: 0.0559 - accuracy: 0.9826 - val_loss: 3.3298 - val_accuracy: 0.5510
Epoch 11/20
237/237 [==============================] - 21s 88ms/step - loss: 0.0505 - accuracy: 0.9840 - val_loss: 3.3499 - val_accuracy: 0.5457
Epoch 12/20
237/237 [==============================] - 21s 89ms/step - loss: 0.0333 - accuracy: 0.9912 - val_loss: 3.5526 - val_accuracy: 0.5664
Epoch 13/20
237/237 [==============================] - 21s 89ms/step - loss: 0.0418 - accuracy: 0.9875 - val_loss: 3.6053 - val_accuracy: 0.5579
Epoch 14/20
237/237 [==============================] - 21s 89ms/step - loss: 0.0330 - accuracy: 0.9889 - val_loss: 3.6405 - val_accuracy: 0.5907
Epoch 15/20
237/237 [==============================] - 21s 90ms/step - loss: 0.0246 - accuracy: 0.9921 - val_loss: 3.9254 - val_accuracy: 0.5505
Epoch 16/20
237/237 [==============================] - 21s 89ms/step - loss: 0.0621 - accuracy: 0.9805 - val_loss: 3.2867 - val_accuracy: 0.5553
Epoch 17/20
237/237 [==============================] - 21s 89ms/step - loss: 0.0427 - accuracy: 0.9869 - val_loss: 3.4953 - val_accuracy: 0.5177
Epoch 18/20
237/237 [==============================] - 21s 89ms/step - loss: 0.0506 - accuracy: 0.9836 - val_loss: 3.6244 - val_accuracy: 0.5547
Epoch 19/20
237/237 [==============================] - 21s 89ms/step - loss: 0.0288 - accuracy: 0.9908 - val_loss: 3.7753 - val_accuracy: 0.5484
Epoch 20/20
237/237 [==============================] - 21s 89ms/step - loss: 0.0233 - accuracy: 0.9929 - val_loss: 4.1841 - val_accuracy: 0.5537
In [14]:
# getting the model's summary
model_base.summary()
Model: "sequential"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 conv2d (Conv2D)             (None, None, None, 16)    448       
                                                                 
 max_pooling2d (MaxPooling2D  (None, None, None, 16)   0         
 )                                                               
                                                                 
 conv2d_1 (Conv2D)           (None, None, None, 32)    4640      
                                                                 
 conv2d_2 (Conv2D)           (None, None, None, 32)    9248      
                                                                 
 max_pooling2d_1 (MaxPooling  (None, None, None, 32)   0         
 2D)                                                             
                                                                 
 conv2d_3 (Conv2D)           (None, None, None, 32)    9248      
                                                                 
 conv2d_4 (Conv2D)           (None, None, None, 32)    9248      
                                                                 
 max_pooling2d_2 (MaxPooling  (None, None, None, 32)   0         
 2D)                                                             
                                                                 
 flatten (Flatten)           (None, None)              0         
                                                                 
 dense (Dense)               (None, 256)               4718848   
                                                                 
 dense_1 (Dense)             (None, 10)                2570      
                                                                 
=================================================================
Total params: 4,754,250
Trainable params: 4,754,250
Non-trainable params: 0
_________________________________________________________________

Evaluating Performance¶

Graphical evaluation¶

In [15]:
# Graphical evaluation of training and validaiton performance 
acc = history_base.history['accuracy']
val_acc = history_base.history['val_accuracy']

loss = history_base.history['loss']
val_loss = history_base.history['val_loss']

epochs_range = range(epochs)

plt.figure(figsize=(11, 8))
plt.subplots_adjust(hspace = .3)
plt.subplot(2, 1, 1)
plt.plot(epochs_range, acc, label = 'Training Accuracy', color = "orange")
plt.plot(epochs_range, val_acc, label = 'Validation Accuracy', color = "blue")
plt.legend(loc = 'best')
plt.xlabel('Epochs')
plt.title('Training and Validation Accuracy', size = 13)

plt.subplot(2, 1, 2)
plt.plot(epochs_range, loss, label = 'Training Loss', color = "orange")
plt.plot(epochs_range, val_loss, label = 'Validation Loss', color = "blue")
plt.legend(loc = 'best')
plt.title('Training and Validation Loss', size = 13)
plt.xlabel('Epochs')

plt.suptitle("Base Model", size=15)
plt.show()

Evaluating model's performance on the test dataset¶

In [16]:
# test loss and accuracy measurments 
test_loss, test_acc = model_base.evaluate(test_batches)
print('Test loss:', test_loss)
print('Test accuracy:', test_acc)
123/123 [==============================] - 7s 58ms/step - loss: 4.0784 - accuracy: 0.5605
Test loss: 4.078429222106934
Test accuracy: 0.5605095624923706

Evaluating the classification performance¶

i) via confussion matrix¶

In [17]:
# getting prediction labales by running the softmax results in argmax
test_labels = test_batches.classes
y_pred = model_base.predict(test_batches)
predicted_lables = np.argmax(y_pred, axis = 1)
cm =  confusion_matrix(test_labels, predicted_lables)
  2/123 [..............................] - ETA: 6s 
2023-03-17 11:11:14.851794: I tensorflow/core/grappler/optimizers/custom_graph_optimizer_registry.cc:113] Plugin optimizer for device_type GPU is enabled.
123/123 [==============================] - 7s 56ms/step
In [18]:
# dataframe containing the confussion matrix
cfm = pd.DataFrame(cm, index = class_names, columns = class_names)
In [19]:
# plotting the confussion matrix
sns.heatmap(cfm, annot=True, fmt='d', cmap='Purples')
plt.xlabel('Predicted Label')
plt.ylabel('True Label')
plt.title('Base Model', size = 15)
plt.xticks(rotation=78)
plt.show()

ii) via designated classification performance evaluators¶

In [20]:
print("Preicision score:", precision_score(test_labels, predicted_lables, average="weighted"))
print("Recall score:", recall_score(test_labels, predicted_lables, average = "weighted"))
print("F1_score:", f1_score(test_labels, predicted_lables, average = "weighted"))
Preicision score: 0.5672258136210193
Recall score: 0.5605095541401274
F1_score: 0.5593190906792165

Testing the model's prediction onto actual images¶

In [21]:
# recall that at the start test_data_shuffles was introduced, it was shuffled
# so that displayed images are not ordered in the same way as in the dataset 
# and variety of classes can be seen 
In [22]:
def right_format_image(pic):
    '''
    This function returns a 
    reshaped image into 224x224 
    format in terms of height and 
    width.
    Further it normalizes the 
    pixel values within the range
    of [0, 1].
    '''
    img_size = (224, 224)
    image = tf.image.resize(pic, img_size)
    image_expanded = np.expand_dims(image, axis=0)
    image_copy = np.copy(image_expanded)
    normalized = image_copy/255.
    return normalized
In [23]:
def data_iterator(data):
    '''
    This function returns as arrays the 
    components of a batch.
    '''
    iterator = data.as_numpy_iterator()
    batch = iterator.next()
    return batch
In [26]:
# plotting images from the test dataset, with their actual and predicted from the model labels 
predicted_batch = data_iterator(test_data_shuffled)

plt.figure(figsize=(12, 12))
plt.subplots_adjust(wspace=.3)
plt.suptitle("Base Model", size = 20)
for i in range(9):
    image, label = predicted_batch[0][i], predicted_batch[1][i]
    predictions = model_base.predict(right_format_image(image))
    prediction_label = class_names[predictions.argmax()]
    ax = plt.subplot(3, 3, i + 1)
    plt.imshow(image.astype(np.uint8))
    plt.title("Actual label:{};\nPredicted label:{}".format(class_names[label],
                                                           class_names[predictions.argmax()]), size = 9)
    plt.axis("off")
1/1 [==============================] - 0s 13ms/step
1/1 [==============================] - 0s 10ms/step
1/1 [==============================] - 0s 9ms/step
1/1 [==============================] - 0s 8ms/step
1/1 [==============================] - 0s 10ms/step
1/1 [==============================] - 0s 8ms/step
1/1 [==============================] - 0s 8ms/step
1/1 [==============================] - 0s 7ms/step
1/1 [==============================] - 0s 9ms/step
In [ ]: